/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2014-06-04
 * V4.0
 */
package com.jphenix.one.boot;

import com.jphenix.share.lang.SString;
import com.jphenix.share.tools.FileCopyTools;
import com.jphenix.share.util.SFilesUtil;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.webserver.interfaceclass.IGlobalVar;

import java.io.File;
import java.io.FilenameFilter;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;

/**
 * 网站启动类
 * com.jphenix.one.boot.WebBootStartup
 * 
 * 当第一个参数为 1 时，为直传参数模式
 * 在该模式下，第二个参数为监听端口
 * 第三个传入参数为虚拟路径
 * 直传参数模式只支持者两个参数 
 * 
 * 2018-06-21 修改了在linux下，自动复制servlet-api.jar文件时报错的问题
 * 
 * @author 刘虻
 * 2009-8-24上午08:57:57
 */
@ClassInfo({"2019-06-15 17:08","网站启动类"})
public class WebBootStartup  {

	protected Method addUrlMethod = null; //添加路径方法
	protected String baseClassPath = null; //类根路径
	
	/**
	 * 构造函数
	 * 2009-8-24上午08:57:57
	 */
	public WebBootStartup() {
		super();
	}

	
	/**
	 * 启动入口
	 * @author 刘虻
	 * 2009-8-24上午08:59:32
	 * @param args 传入参数
	 */
	public static void main(String[] args) {
		//构建启动类
		WebBootStartup ws = new WebBootStartup();
		try {
			//启动
			ws.start(args);
		}catch(Exception e) {
			e.printStackTrace();
			System.out.println("\n\n13-----------------------------------------------------------------------\nThe Application Has Bean Stoped\n--------------------------------------------------------------------------------------\n");
			System.exit(1);
		}
	}
	
	/**
     * 获取完整路径
     * @author 刘虻
     * 2007-6-18下午04:21:49
     * @param dummyPath 相对路径
     * @return 完整路径
     */
    protected String getAllFilePath(String dummyPath) {
    	
    	//类根路经
    	String baseClassPath = getBaseClassPath();
    	if (dummyPath==null) {
    		return baseClassPath;
    	}
    	//转换分割符
    	dummyPath = swapString(dummyPath,"\\","/");
    	//是否为根路经
    	boolean isBase = false;
    	if (dummyPath.startsWith("//")
    			|| dummyPath.startsWith("zip:")
    			|| dummyPath.startsWith("file:")
    			|| dummyPath.indexOf(":/")==1
    			|| dummyPath.indexOf(":/")==2
    			|| getFristPath(dummyPath).equals(getFristPath(getBaseClassPath()))) {
    		isBase = true;
    	}
		if (!isBase) {
			if (!dummyPath.startsWith("/")) {
				dummyPath = "/"+dummyPath;
			}
			dummyPath = baseClassPath+dummyPath;
		}
    	dummyPath = swapString(dummyPath,"///","/");
    	dummyPath = swapString(dummyPath,"//","/");
    	StringBuffer rePathSbf = new StringBuffer(); //构造返回路径
    	if (dummyPath.startsWith("/")) {
    		rePathSbf.append("/");
    		dummyPath = dummyPath.substring(1);
    	}else if (dummyPath.indexOf(":/")==1) {
    		rePathSbf.append(dummyPath, 0, 3);
    		dummyPath = dummyPath.substring(3);
    	}else if (dummyPath.indexOf(":/")==2) {
    		rePathSbf.append(dummyPath, 0, 4);
    		dummyPath = dummyPath.substring(4);
    	}
    	//分割成文件夹名
    	ArrayList<String> pathSubList = splitToList(dummyPath,"/");
    	//整理后的文件夹序列
    	ArrayList<String> fixList = new ArrayList<String>();
    	boolean isWar = false; //是否为war
    	if (pathSubList!=null) {
    		for (String thisPath:pathSubList) {
    			//endsWith("!") 是因为如果部署成war格式 war文件名与展开时的根路径名不同
    			if ("..".equals(thisPath)) {
    				if (fixList.size()>0 ) {
    					if (SString.valueOf(fixList.get(fixList.size()-1)).endsWith("!")) {
    						isWar = true;
    					}else {
    						fixList.remove(fixList.size()-1);
    					}
    				}
    			}else {
    				if (isWar) {
    					isWar = false;
    				}else {
    					fixList.add(thisPath);
    				}
    			}
    		}
    	}
    	for (int i=0;i<fixList.size();i++) {
    		if (i>0) {
    			rePathSbf.append("/");
    		}
    		rePathSbf.append(fixList.get(i));
    	}
    	return rePathSbf.toString();
    }
    
    
    /**
     * 获取第一个文件夹
     * @author 刘虻
     * 2008-1-30下午08:31:01
     * @param path 全路径
     * @return 第一个文件夹
     */
    protected String getFristPath(String path) {
    	while(path.startsWith("/")) {
    		path = path.substring(1);
    	}
    	//文件夹分割点
    	int point = path.indexOf("/");
    	if (point>-1) {
    		return path.substring(0,point);
    	}
    	return "";
    }
    
    
    /**
     * 获取类的根路径
     * 
     * 如果这个FilesUtil类在Jar包中
     * 并且Servlet容器将这个Jar包复制到缓存文件夹中运行
     * 则无法根据自身定位到类的根路径
     * 
     * 所以需要指定一个在根路径中的类
     * 
     * @author 刘虻
     * 2009-7-17上午09:43:01
     * @return
     */
    public String getBaseClassPath() {
    	if (baseClassPath==null) {
    		//获取当前类URL
			URL url = 
				(this.getClass().getProtectionDomain()).getCodeSource().getLocation();
			baseClassPath = url.getPath();
			//获取路径
			try {
				//从Class中获取到的路径都是UTF-8编码格式的
				baseClassPath = URLDecoder.decode(baseClassPath,"UTF-8");
			}catch(Exception e) {}
			if (baseClassPath.indexOf(".jar")>-1) {
				//因为该类在jar包中，所以获取这个jar包的路径
				baseClassPath = getFilePath(baseClassPath);
			}else {
				//获取类相对路径
				String classPath = 
					this.getClass().getName().replace('.', '/').concat(".class");
				if (baseClassPath==null) {
					baseClassPath = "";
				}
				//获取节点
				int point = baseClassPath.indexOf(classPath);
				if (point>-1) {
					baseClassPath = baseClassPath.substring(0,point);
				}
			}
			baseClassPath = swapString(baseClassPath,"\\","/");
    	}
    	return baseClassPath;
    }
    
    
    
    /**
     * 从文件全路径中获取文件路径
     * @author 刘虻
     * 2006-9-13下午02:54:15
     * @param filePathStr 文件全路径
     * @return 文件路径
     */
    public String getFilePath(String filePathStr) {
        //导入参数合法化
        if (filePathStr == null 
                || filePathStr.length()==0) {
            return null;
        }
        //整理路径
        filePathStr = 
            swapString(filePathStr,"\\","/");
      
        int lastPoint = filePathStr.lastIndexOf("/"); //最后的位置
        if (lastPoint>0) {
        	return filePathStr.substring(0,filePathStr.lastIndexOf("/"));
        }
        return "/";
    }
    
    
    
	/**
	 * 将字符串按照指定字符分割为字符串数组
	 * @author 刘虻
	 * 2006-10-26下午11:54:09
	 * @param source 原字符串
	 * @param splitStr 指定分割字符
	 * @return 分割后的字符串数组
	 */
	public static String[] split(String source,String splitStr) {
		//获取分割后的序列
		ArrayList<String> reArrl = splitToList(source,splitStr);
		//构件返回值
		String[] reStrs = new String[reArrl.size()];
		//复制值
		reArrl.toArray(reStrs);
		return reStrs;
	}
	
    
    
    /**
     * 替换字符串中指定的字符
     * @author 刘虻
     * @param str 要操作的字符串
     * @param fromStr 需要替换的字符串
     * @param toStr 替换成的字符串
     * @return 替换后的字符串
     * 2006-1-5  10:10:04
     */
    public static String swapString(String str,String fromStr,String toStr) {
        
        //导入参数合法化
        if (str==null) {
            return null;
        }
        String[] strs = split(str,fromStr);
        StringBuffer reStr = new StringBuffer();
        if (strs!=null && strs.length>0) {
            for (int i=0;i<strs.length;i++) {
                reStr.append(strs[i]); 
                if (i<strs.length-1) {
                    reStr.append(toStr); 
                }
            }
        }
        return reStr.toString();
    }
    
    
	/**
	 * 从字符串序列转换为序列
	 * @author 刘虻
	 * 2007-5-30下午08:31:55
	 * @param source 源字符串
	 * @param point 分割符
	 * @return 转换后的序列
	 */
	public static ArrayList<String> splitToList(String source,String point) {
		
		if (source==null || source.length()==0) {
			return new ArrayList<String>();
		}
		//构造返回容器
		ArrayList<String> reArrl = new ArrayList<String>();
		while(true) {
			//获取分割点
			int splitPoint = source.indexOf(point);
			if (splitPoint<0) {
				reArrl.add(source);
				break;
			}
			reArrl.add(source.substring(0,splitPoint));
			//构造分割后的字符串
			source = 
				source.substring(splitPoint+point.length());
		}
		return reArrl;
	}
	
	/**
	 * 添加多个类路径
	 * @author 刘虻
	 * 2009-8-21下午01:38:09
	 * @param morePath 用分号分割的多个类路径字符串
	 * @throws Exception 执行发生异常
	 */
	public void addMorePath(String morePath) throws Exception {
		//获取多个类路径
		String[] classPaths = null;
		if(morePath.indexOf(";")>-1) {
			classPaths = morePath.split(";");
		}else {
			classPaths = 
				morePath.split(System.getProperty("path.separator"));
		}
		addMorePath(classPaths);
	}
	
	
	/**
	 * 获取加载路径方法
	 * @author 刘虻
	 * 2009-8-21下午01:31:29
	 * @return 加载路径方法
	 * @throws Exception 执行发生异常
	 */
	protected Method getAddUrlMethod() throws Exception {
		if(addUrlMethod==null) {
			addUrlMethod = 
				URLClassLoader.class
					.getDeclaredMethod("addURL", URL.class);
			addUrlMethod.setAccessible(true);
		}
		return addUrlMethod;
	}
	
	/**
	 * 执行添加路径
	 * @author 刘虻
	 * 2009-8-21下午01:33:28
	 * @param url 待添加的路径
	 * @throws Exception 执行发生异常
	 * @deprecated
	 */
	public void addURL(URL url) throws Exception {
		getAddUrlMethod()
			.invoke(ClassLoader.getSystemClassLoader(), url);
		getAddUrlMethod()
			.invoke(Thread.currentThread().getContextClassLoader(), url);
	}
	
	
	/**
	 * 执行添加指定文件路径
	 * @author 刘虻
	 * 2009-8-21下午03:36:35
	 * @param filePath 文件路径
	 * @throws Exception 执行发生异常
	 * @deprecated
	 */
    public void addFilePath(String filePath) throws Exception {
		getAddUrlMethod()
			.invoke(
					ClassLoader.getSystemClassLoader()
					, (new File(getAllFilePath(filePath)).toURL()));
	}
	
	/**
	 * 执行添加路径
	 * @author 刘虻
	 * 2009-8-21下午01:34:58
	 * @param path 待添加的路径
	 * @throws Exception 执行发生异常
	 */
	public void addPath(String path) throws Exception {
		
		//搜索到的文件序列
		ArrayList<String> fileList = new ArrayList<String>();
		fileList.add(path);
		
		//扩展名序列
		ArrayList<String> extNameList = new ArrayList<String>();
		extNameList.add("jar");
		extNameList.add("zip");
		
		//执行搜索
		doSetFilePath(fileList,path,extNameList,true);
		for(String filePath:fileList) {
			if(filePath==null || filePath.length()<1) {
				continue;
			}
			addFilePath(filePath);
		}
	}
	
	

    /**
	 * 获得指定路径下，符合文件类型的所有文件路径
	 * @author 刘虻
	 * @param filePathArrl 存放搜索后的文件路径
	 * @param basePathStr 指定根文件夹路径（末尾不加/)
	 * @param extNameList 文件扩展名 为空或者*则搜索所有文件  为/则只搜索文件夹名
	 * @param includeChildBoo 是否包含子路径
	 * Exception 获得文件路径失败
	 * 2006-4-1  11:26:00
	 */
    public void doSetFilePath (
            ArrayList<String> filePathArrl
            ,String basePathStr
            ,List<String> extNameList
            ,boolean includeChildBoo) throws Exception {

    	//校验扩展名序列
    	final ArrayList<String> checkNameList = new ArrayList<String>();
    	if(extNameList!=null) {
	    	//转换为小写
	    	for(Object element:extNameList) {
	    		if(element==null) {
	    			continue;
	    		}
	    		checkNameList.add(((String)element).toLowerCase());
	    	}
    	}
    	basePathStr = getAllFilePath(basePathStr);
    	//构建根路径对象
    	File file = new File(basePathStr);
        //获得当前路径下，所有符合条件的文件和文件夹
        String[] filePathListStrs = 
        	file.list(new FilenameFilter() {
            //文件筛选类
            @Override
            public boolean accept(File dirPathFile, String lastNameStr) {
            	lastNameStr = lastNameStr.toLowerCase(); //转换为小写
                //构建查询到的文件或路径
                File file = null;
                    try{
                    	if (lastNameStr==null || "/".equals(lastNameStr)) {
                    		file = new File(dirPathFile.getPath());
                    	}else {
                    		file = new File(dirPathFile.getPath()+"/"+lastNameStr);
                    	}
                    }catch(Exception e) {
                        e.printStackTrace();
                    }
                if (file != null && file.isDirectory()) {
                    //判断是否为文件夹
                    return true;
                }else {
                	//扩展名分割点
                	int point = lastNameStr.lastIndexOf(".");
                	String extName = ""; //扩展名
                	if(point>-1) {
                		extName = lastNameStr.substring(point+1);
                	}
					//是否为指定扩展名的文件
					return checkNameList.size() < 1
							|| checkNameList.contains("*")
							|| checkNameList.contains(extName);
                }
            }
        });
        //判断是否存在文件或文件夹
        if (filePathListStrs != null && filePathListStrs.length >0) {
            //循环筛选
        	String bPath = basePathStr; //处理过的根路径
        	if (bPath.endsWith("/")) {
        		bPath = bPath.substring(0,bPath.length()-1);
        	}
            for ( int i=0; i< filePathListStrs.length; i++){
                //构建查询到的文件或路径
                String findFilePath = bPath+"/"+filePathListStrs[i];
                //构造查询到的文件或文件夹
                File findFile = getFileByName(findFilePath);
                if (findFile.isDirectory() && includeChildBoo) {
                    //如果是文件夹，并且搜索子文件夹，则递归调用方法
                	doSetFilePath(filePathArrl,findFilePath,checkNameList,includeChildBoo);
                    if (checkNameList.size()<1) {
                    	continue;
                    }
                    if (checkNameList.contains("*") || checkNameList.contains("/")) {
                    	filePathArrl.add(findFilePath);
                    }
                }else {
                    //加入文件路径
                    filePathArrl.add(findFilePath);
                }
    	    }
        }
    }
    
    
    /**
     * 通过URL方式获得文件对象
     * @author 刘虻
     * @param filePathStr 文件相对路径
     * @return 对应的文件对象
     * @exception Exception 获取文件时发生异常
     * 2006-4-8  14:01:57
     */
    public File getFileByName(
            String filePathStr) throws Exception {
    	//构造返回值
    	File reFile = new File(getAllFilePath(filePathStr));
    	if (!reFile.exists()) {
    		throw new Exception("No Find The File By Path:["+filePathStr+"] AllPath:["+reFile.getPath()+"] BasePath:["+getBaseClassPath()+"]");
    	}
        return reFile;
    }
	
	/**
	 * 添加多个类路径
	 * @author 刘虻
	 * 2009-8-21下午01:39:17
	 * @param morePaths 类路径数组
	 * @throws Exception 执行发生异常
	 */
	public void addMorePath(String[] morePaths) throws Exception {
		if(morePaths==null) {
			return;
		}
		for(int i=0;i<morePaths.length;i++) {
			if(morePaths[i]==null) {
				continue;
			}
			addPath(morePaths[i]);
		}
	}
	
	
	/**
	 * 获取当前类加载器中的路径信息数组
	 * @author 刘虻
	 * 2009-10-20下午04:40:09
	 * @return 当前类加载器中的路径信息数组
	 */
	protected URL[] getUrls() {
		try {
			//获取方法
			Method getUrlsMethod = 
					URLClassLoader.class
						.getDeclaredMethod("getURLs");
			getUrlsMethod.setAccessible(true);
			return (URL[])getUrlsMethod
							.invoke(
									ClassLoader.getSystemClassLoader()
									,new Object[] {});
		}catch(Exception e) {
			e.printStackTrace();
		}
		return new URL[0];
	}
	
	/**
	 * 执行启动类
	 * @author 刘虻
	 * 2009-8-21下午01:46:37
	 * @param bootClassPath 启动类路径
	 * @param args 传入参数
	 * @throws Exception 执行发生异常
	 */
	public void executeBootClass(String bootClassPath,String[] args) throws Exception {
		//构建启动类
		Class<?> cls = Class.forName(bootClassPath);
		//获取启动方法
		Method main = cls.getMethod("main", String[].class);
		//执行启动
		main.invoke(null,new Object[] {args});
	}
	
	/**
	 * 启动方法
	 * 
     * 当第一个参数为 1 时，为直传参数模式
     * 在该模式下，第二个参数为监听端口
     * 第三个传入参数为虚拟路径
     * 直传参数模式只支持者两个参数 
	 * 
	 * @author 刘虻
	 * 2009-8-24上午09:00:17
	 * @param args 传入参数
	 * @throws Exception 执行发生异常
	 */
	public void start(String[] args) throws Exception {
		//添加类路径
		addMorePath("/../lib;/../classes");
		//类路径信息
		StringBuffer classPathSbf = new StringBuffer();
		//获取类路径数组
		URL[] urls = getUrls();
		if(urls.length>0) {
			classPathSbf.append(">Load ClassPath:\n");
			for(int i=0;i<urls.length;i++) {
				classPathSbf.append(urls[i]).append("\n");
			}
		}else {
			classPathSbf.append(">No ClassPath");
		}
		classPathSbf.append("\n");
		System.out.println(classPathSbf.toString());
		
		try {
			this.getClass().getClassLoader().loadClass("javax.servlet.FilterConfig");
		}catch(Exception e) {
			useNativeServletApi(); //使用内置的Servlet-API.jar
		}
		
		//运行根路径
		if(args!=null && args.length>0 && args[0].trim().length()>1) {
			args[0]= getAllFilePath(args[0]+"/WEB-INF/resfiles/"+IGlobalVar.CONFIG_TEXT_FILE_NAME);
		}
		//执行启动
		executeBootClass("com.jphenix.webserver.instancea.Serve",args);
	}
	
	/**
	 * 使用内置的Servlet-API.jar
	 * 2016年12月8日
	 * @author MBG
	 */
	@SuppressWarnings("deprecation")
	public void useNativeServletApi() {
		//库文件名
		String fileName = "servlet-api.jar";
		//临时文件夹
		String tempPath = System.getProperty("java.io.tmpdir");
		//是否之前已经做过解压缩
		File apiFile = new File(tempPath+"/"+fileName);
		InputStream is = null; //文件读入流
		try {
			if(apiFile.exists()) {
				addURL(apiFile.toURL());
				return;
			}
			//资源路径
			URL baseUrl = this.getClass().getClassLoader().getResource("resources/lib/servlet-api.jar");
			if(baseUrl==null) {
				return;
			}
			is = SFilesUtil.getZipFileIsByUrl(baseUrl.toString());
			if(is==null) {
				return;
			}
			FileCopyTools.copy(is,apiFile);
			addURL(apiFile.toURL());
			System.out.println("Not Find The File:[servlet-api.jar] Is Already Created To:["+apiFile.getPath()+"]");
		}catch(Exception e) {
			e.printStackTrace();
			System.err.println("Temp Path:["+tempPath+"] CopyFile:["+apiFile.getPath()+"]");
		}finally {
			try {
				is.close();
			}catch(Exception e) {}
		}
	}
}
